package ua.yandex.collections;

public class MyArrayList implements MyList {

    private static final int DEFAULT_LENGTH = 10;
    private static final int LENGTH_OF_EMPTY = 0;
    private static final double INCREMENT = 1.5;
    private Object[] array;
    private int size = 0;

    public MyArrayList(int initialCapacity) {
        array = new Object[initialCapacity];
    }

    public MyArrayList() {
        this(DEFAULT_LENGTH);
    }

    private void validIndex(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index is invalid.");
        }
    }

    private boolean arrayNeedsToIncrease(int sizeOfSpace) {
        int newSize = sizeOfSpace + size;
        return newSize > array.length;
    }

    private void advancedArrayCopy(Object[] newArray,
                                   int position, int sizeOfSpace) {
        int endOfSpace = position + sizeOfSpace;
        int numbersOfElementsAfterPosition = size - position;
        if (numbersOfElementsAfterPosition > 0) {
            System.arraycopy(array, position,
                    newArray, endOfSpace, numbersOfElementsAfterPosition);
        }
        if (newArray != array) {
            System.arraycopy(array, 0, newArray, 0, position);
        }
        array = newArray;
        size += sizeOfSpace;
    }

    private void makeSpace(int position, int sizeOfSpace) {
        if (arrayNeedsToIncrease(sizeOfSpace)) {
            int newSize = (int) ((sizeOfSpace + size) * INCREMENT);
            Object[] newArray = new Object[newSize];
            advancedArrayCopy(newArray, position, sizeOfSpace);
        } else {
            advancedArrayCopy(array, position, sizeOfSpace);
        }
    }

    public void ensureCapacity(int minCapacity) {
        int difference = minCapacity - size;
        if (arrayNeedsToIncrease(difference)) {
            Object[] newArray = new Object[minCapacity];
            System.arraycopy(array, 0, newArray, 0, size);
            array = newArray;
        }
    }

    @Override
    public void add(Object e) {
        int positionToInsert = size;
        makeSpace(positionToInsert, 1);
        array[positionToInsert] = e;
    }

    @Override
    public void add(int index, Object e) {
        validIndex(index);
        makeSpace(index, 1);
        array[index] = e;
    }

    @Override
    public void addAll(Object[] c) {
        int positionToInsert = size;
        makeSpace(positionToInsert, c.length);
        System.arraycopy(c, 0, array, positionToInsert, c.length);
    }

    @Override
    public void addAll(int index, Object[] c) {
        validIndex(index);
        makeSpace(index, c.length);
        System.arraycopy(c, 0, array, index, c.length);
    }

    @Override
    public Object get(int index) {
        validIndex(index);
        return array[index];
    }

    @Override
    public Object remove(int index) {
        validIndex(index);
        Object removedObject = array[index];
        Object[] newArray = new Object[array.length - 1];
        System.arraycopy(array, 0, newArray, 0, index);
        int numbersOfElementsAfterIndex = size - index - 1;
        System.arraycopy(array, index + 1,
                newArray, index, numbersOfElementsAfterIndex);
        --size;
        array = newArray;
        return removedObject;
    }

    @Override
    public void set(int index, Object e) {
        validIndex(index);
        array[index] = e;
    }

    @Override
    public int indexOf(Object o) {
        if (o == null) {
            for (int elementIndex = 0; elementIndex < size; elementIndex++) {
                if (array[elementIndex] == null) {
                    return elementIndex;
                }
            }
        } else {
            for (int elementIndex = 0; elementIndex < size; elementIndex++) {
                if (array[elementIndex].equals(o)) {
                    return elementIndex;
                }
            }
        }
        return -1;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        array = new Object[LENGTH_OF_EMPTY];
        size = 0;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public Object[] toArray() {
        Object[] toArray = new Object[size];
        System.arraycopy(array, 0, toArray, 0, size);
        return toArray;
    }

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("[");
        for (int index = 0; index < size; index++) {
            if (array[index] == null) {
                string.append("null");
            } else {
                string.append(array[index].toString());
            }
            if (index < size - 1) {
                string.append(", ");
            }
        }
        string.append("]");
        return string.toString();
    }
}

